package com.xh.shuati.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.rholder.retry.*;
import com.google.common.hash.Hashing;
import com.xh.shuati.common.ErrorCode;
import com.xh.shuati.exception.BusinessException;
import com.xh.shuati.mapper.PostThumbMapper;
import com.xh.shuati.model.entity.Post;
import com.xh.shuati.model.entity.PostThumb;
import com.xh.shuati.model.entity.User;
import com.xh.shuati.rocketmq.event.ThumbEvent;
import com.xh.shuati.rocketmq.producer.ThumbProducer;
import com.xh.shuati.service.PostService;
import com.xh.shuati.service.PostThumbService;
import javax.annotation.Resource;

import com.xh.shuati.utils.SpringContextUtils;
import org.redisson.api.RKeys;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.messaging.MessagingException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * 帖子点赞服务实现
 *
 * @author <a href="https://github.com/canon0521">xh</a>
 */
@Service
public class PostThumbServiceImpl extends ServiceImpl<PostThumbMapper, PostThumb>
        implements PostThumbService {

    /**
     * 点赞帖子前缀
     */
    private static final String POST_THUMB_KEY = "PT:";

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private PostService postService;

    @Resource
    private ThumbProducer thumbProducer;

    /**
     * Lua脚本点赞
     */
    private static final String THUMB_SCRIPT =
            """
            local userThumbBlogKey = KEYS[1]
            local time = ARGV[1]
    
            -- 获取当前状态
            if redis.call("GET", userThumbBlogKey) == false then
                -- 如果不存在，设置点赞时间
                redis.call('SET', userThumbBlogKey, time)
                return 1
            else
                -- 如果已存在，删除表示取消点赞
                redis.call("DEL", userThumbBlogKey)
                return -1
            end
            """;
    @Autowired
    private SpringContextUtils springContextUtils;

    /**
     * 点赞
     *
     * @param postId
     * @param loginUser
     * @return
     */
    @Override
    public int doPostThumb(Long postId, User loginUser) {
        // 判断实体是否存在，根据类别获取实体
        Post post = postService.getPostById(postId);
        if (post == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND);
        }

        //1.定义重试策略
        Retryer<Integer> retryer = RetryerBuilder.<Integer>newBuilder()
                .retryIfException() // 发生异常时重试
                .retryIfResult(result -> result == 0) // 返回0时重试
                .withWaitStrategy(WaitStrategies.exponentialWait(100, 5, TimeUnit.SECONDS)) // 指数退避
                .withStopStrategy(StopStrategies.stopAfterAttempt(3)) // 最多重试3次
                .build();

        // 是否已点赞
        Long userId = loginUser.getId();
        String key = POST_THUMB_KEY + userId + ":" + postId;
        String generateKey = generateLikeKey(key);

        try {
            return retryer.call(() -> {
                try {
                    //2.1判断是否点赞(Lua)
                    Long executed = stringRedisTemplate.execute(new DefaultRedisScript<>(THUMB_SCRIPT, Long.class)
                            , Collections.singletonList(generateKey), String.valueOf(System.currentTimeMillis()));
                    //2.2生成实体类
                    ThumbEvent thumbEvent = new ThumbEvent();
                    thumbEvent.setUserId(userId);
                    thumbEvent.setPostId(postId);
                    thumbEvent.setDateTime(LocalDateTime.now());
                    thumbEvent.setIsThumb(executed == 1 ? ThumbEvent.ThumbType.THUMB : ThumbEvent.ThumbType.CANCEL_THUMB);

                    //2.3添加到消息队列
                    thumbProducer.AddThumb(thumbEvent);
                    return executed.intValue();

                } catch (MessagingException e) {
                    return 0;
                }
            });
        } catch (BusinessException | ExecutionException | RetryException e) {
            throw new RuntimeException(e);
        }
        // 每个用户串行点赞
        // 锁必须要包裹住事务方法
//        PostThumbService postThumbService = (PostThumbService) AopContext.currentProxy();
//        synchronized (String.valueOf(userId).intern()) {
//            return postThumbService.doPostThumbInner(userId, postId);
//        }
    }

    @Override
    public Boolean hasThumb(Long postId, Long userId) {
        String key = POST_THUMB_KEY + userId + ":" + postId;
        String generateKey = generateLikeKey(key);
        return redissonClient.getBucket(generateKey).isExists();
    }

    /**
     * 封装了事务的方法
     *
     * @param userId
     * @param postId
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int doPostThumbInner(long userId, long postId) {
        PostThumb postThumb = new PostThumb();
        postThumb.setUserId(userId);
        postThumb.setPostId(postId);
        QueryWrapper<PostThumb> thumbQueryWrapper = new QueryWrapper<>(postThumb);
        PostThumb oldPostThumb = this.getOne(thumbQueryWrapper);
        boolean result;
        // 已点赞
        if (oldPostThumb != null) {
            result = this.remove(thumbQueryWrapper);
            if (result) {
                //更新post 点赞数 - 1
                result = postService.update()
                        .eq("id", postId)
                        .gt("thumbNum", 0)
                        .setSql("thumbNum = thumbNum - 1")
                        .update();
                return result ? -1 : 0;
            } else {
                throw new RuntimeException("用户已点赞");
            }
        } else {
            // 未点赞
            result = this.save(postThumb);
            if (result) {
                //更新post 点赞数 + 1
                result = postService.update()
                        .eq("id", postId)
                        .setSql("thumbNum = thumbNum + 1")
                        .update();
                return result ? 1 : 0;
            } else {
                throw new BusinessException(ErrorCode.SYSTEM_ERROR);
            }
        }
    }

    /**
     * 使用压缩算法MurmurHash3,生成压缩Key
     * @param original
     * @return 压缩后的Key
     */
    //Todo 概率极低，MurmurHash3冲突率约1/2^64
    public String generateLikeKey(String original) {
        long hash = Hashing.murmur3_128().hashString(original, StandardCharsets.UTF_8).asLong();
        // 转为16进制缩短长度
        return "PT:" + Long.toHexString(hash);
    }



}




